package com.rags.chess.game;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.rags.chess.base.Color;
import com.rags.chess.base.Coordinate;
import com.rags.chess.base.Piece;
import com.rags.chess.base.Square;
import com.rags.chess.move.BasicMove;
import com.rags.chess.move.IllegalMoveException;
import com.rags.chess.move.KingsideCastling;
import com.rags.chess.move.Move;
import com.rags.chess.move.PawnPromotionMove;
import com.rags.chess.move.QueensideCastling;
import com.rags.chess.move.Resign;
import com.rags.chess.pieces.Bishop;
import com.rags.chess.pieces.King;
import com.rags.chess.pieces.Knight;
import com.rags.chess.pieces.Pawn;
import com.rags.chess.pieces.Queen;
import com.rags.chess.pieces.Rook;

public class Game {
	private static Log log = LogFactory.getLog(Game.class);
	private Square[][] squares = new Square[8][8];
	private HashMap<Coordinate, Square> squareCoordinates = new HashMap<Coordinate, Square>();
	private Collection<Piece> pieces;
	private Color moveTurn;
	private HashMap<Color, Player> players;
	private String result;
	private List<Move> moves;
	private String event = "";
	private String date = "????.??.??";
	private String board = "";
	
	public Game(Player whitePlayer, Player blackPlayer) {
		initializeSquares();
		pieces = new Vector<Piece>();
		moveTurn = Color.WHITE;
		result = "";
		moves = new Vector<Move>();
		
		setupPieces();
		
		players = new HashMap<Color, Player>();
		players.put(Color.WHITE, whitePlayer);
		players.put(Color.BLACK, blackPlayer);
	}

	public Game(Game game) {
		initializeSquares();
		pieces = new Vector<Piece>();
		for (Piece p : game.pieces) {
			Piece newPiece = p.clone();
			pieces.add(newPiece);
			getSquare(newPiece.getPosition()).setPiece(newPiece);
		}
		moveTurn = game.moveTurn;
		result = game.result;
		
		players = new HashMap<Color, Player>();
		players.put(Color.WHITE, game.getPlayer(Color.WHITE));
		players.put(Color.BLACK, game.getPlayer(Color.BLACK));
		
		duplicateMoves(game);
	}

	private void initializeSquares() {
		for (int i=0; i<squares.length; i++)
			for (int j=0; j<squares[i].length; j++) {
				Coordinate coordinate = new Coordinate(i, j);
				squares[i][j] = new Square(coordinate);
				squareCoordinates.put(coordinate, squares[i][j]);
			}
	}
	
	private void duplicateMoves(Game aGame) {
		moves = new Vector<Move>();
		for (int i=0; i<aGame.moves.size(); i++)
			try {
				moves.add((Move)aGame.moves.get(i).clone());
				moves.get(i).setGame(this);
			} catch (CloneNotSupportedException e) {
				log.warn(String.format("Unable to clone move!", aGame.moves.get(i).getNotation()));
			}
	}
	
	private void setupPieces() {
		// setup pawns
		String columns = "abcdefgh";
		for (int i=0; i<columns.length(); i++) {
			char column = columns.charAt(i);
			setupPawn(Color.WHITE, new Coordinate(String.format("%c2", column)));
			setupPawn(Color.BLACK, new Coordinate(String.format("%c7", column)));
		}
		
		// setup knights
		setupKnight(Color.WHITE, new Coordinate("b1"));
		setupKnight(Color.WHITE, new Coordinate("g1"));
		setupKnight(Color.BLACK, new Coordinate("b8"));
		setupKnight(Color.BLACK, new Coordinate("g8"));
		
		// setup bishops
		setupBishop(Color.WHITE, new Coordinate("c1"));
		setupBishop(Color.WHITE, new Coordinate("f1"));
		setupBishop(Color.BLACK, new Coordinate("c8"));
		setupBishop(Color.BLACK, new Coordinate("f8"));
		
		// setup rooks
		setupRook(Color.WHITE, new Coordinate("a1"));
		setupRook(Color.WHITE, new Coordinate("h1"));
		setupRook(Color.BLACK, new Coordinate("a8"));
		setupRook(Color.BLACK, new Coordinate("h8"));
		
		// setup queens
		setupQueen(Color.WHITE, new Coordinate("d1"));
		setupQueen(Color.BLACK, new Coordinate("d8"));
		
		// setup kings
		setupKing(Color.WHITE, new Coordinate("e1"));
		setupKing(Color.BLACK, new Coordinate("e8"));
	}

	private void setupPiece(Coordinate coordinate, Piece piece) {
		getSquare(coordinate).setPiece(piece);
		pieces.add(piece);
		log.debug(String.format("Adding a %s %s to %s", piece.getColor(), piece.getClass().getSimpleName(), coordinate));
	}
	
	private void setupPawn(Color color, Coordinate coordinate) {
		Piece piece = new Pawn(color, coordinate);
		setupPiece(coordinate, piece);
	}

	private void setupKnight(Color color, Coordinate coordinate) {
		Piece piece = new Knight(color, coordinate);
		setupPiece(coordinate, piece);
	}
	
	private void setupBishop(Color color, Coordinate coordinate) {
		Piece piece = new Bishop(color, coordinate);
		setupPiece(coordinate, piece);
	}
	
	private void setupRook(Color color, Coordinate coordinate) {
		Piece piece = new Rook(color, coordinate);
		setupPiece(coordinate, piece);
	}
	
	private void setupQueen(Color color, Coordinate coordinate) {
		Piece piece = new Queen(color, coordinate);
		setupPiece(coordinate, piece);
	}
	
	private void setupKing(Color color, Coordinate coordinate) {
		Piece piece = new King(color, coordinate);
		setupPiece(coordinate, piece);
	}
	
	public void startGame() {
		while (true) {
			if (GameHelper.isInStalemate(this, moveTurn)) {
				result = "1/2-1/2";
				return;
			}
			log.debug(String.format("%s's turn to move", moveTurn));
			Move move = players.get(moveTurn).makeMove(this);
			log.debug(String.format("%s's move: %s", moveTurn, move.getNotation()));
			if (move instanceof Resign) {
				result = moveTurn.equals(Color.WHITE) ? "0-1" : "1-0";
				return;
			}
			if (move.getNotation().endsWith("#")) {
				result = moveTurn.equals(Color.WHITE) ? "1-0" : "0-1";
				return;
			}
			moves.add(move);
			moveTurn = moveTurn.equals(Color.WHITE)? Color.BLACK : Color.WHITE;
		}
	}

	public Color getMoveTurn() {
		return moveTurn;
	}

	public void setMoveTurn(Color aMoveTurn) {
		this.moveTurn = aMoveTurn;
	}

	public Collection<Square> getSquares() {
		return squareCoordinates.values();
	}
	
	public Square getSquare(Coordinate position) {
		return squareCoordinates.get(position);
	}

	public Collection<Piece> getPieces(Color color) {
		Collection<Piece> coloredPieces = new Vector<Piece>();
		for (Piece p : pieces)
			if (color.equals(p.getColor()))
				coloredPieces.add(p);
		return coloredPieces;
	}
	
	public Piece getPiece(Coordinate position) {
		return getSquare(position).getPiece();
	}
	
	public void addPiece(Piece piece) {
		pieces.add(piece);
	}
	
	public void removePiece(Piece piece) {
		pieces.remove(piece);
	}
	
	public Move createMove(Player player, Coordinate start, Coordinate end) {
		if (!player.getColor().equals(moveTurn))
			throw new IllegalMoveException(String.format("Illegal move turn: %s", player.getColor()));
		
		try {
			return new BasicMove(this, start, end);
		} catch(IllegalArgumentException e) {
			try {
				return new KingsideCastling(this, start, end);
			} catch (IllegalArgumentException e2) {
				try {
					return new QueensideCastling(this, start, end);
				} catch (IllegalArgumentException e3) {
					try {
						return new PawnPromotionMove(this, start, end, player);
					} catch(IllegalArgumentException e4) {
						throw new IllegalMoveException(String.format("Illegal move: %s-%s", start, end));
					}
				}
			}
		}
	}

	public String getResult() {
		return result;
	}

	public void setResult(String result) {
		this.result = result;
	}

	public Player getPlayer(Color color) {
		return players.get(color);
	}

	public Move getLastMove() {
		return moves == null || moves.size() == 0 ? null : moves.get(moves.size()-1);
	}

	public void setMoves(List<Move> moves) {
		this.moves = moves;
	}

	public String getOpening() {
		return "";
	}

	public String getECO() {
		return "";
	}

	public String getVariant() {
		return "";
	}

	public int getPlyCount() {
		return moves.size();
	}

	public List<Move> getMoves() {
		return moves;
	}
	
	public String getEvent() {
		return event;
	}
	
	public String getDate() {
		return date;
	}
	
	public String getBoard() {
		return board;
	}

	protected void setEvent(String event) {
		this.event = event;
	}

	protected void setDate(String date) {
		this.date = date;
	}

	protected void setBoard(String board) {
		this.board = board;
	}
}
